<?php
/**
 +------------------------------------------------------------------------------
 * framework 入口文件
 +------------------------------------------------------------------------------
 * @Version 3.0
 * @Author  WangXian
 * @E-mail   admin@loopx.cn
 * @package ephp
 * @Creation date 2010-9-8
 * @Modified date 2010-12-21 14:22:34
 +------------------------------------------------------------------------------
 */
if( ! defined('APP_PATH')) exit;

#系统运行信息标记
$run_start_time = microtime(1);	#启动时间
$run_dbquery_count = 0;			#执行了多少次数据库查询

///////////////////////////////////////////////////////////////////////////////////
// common functions
///////////////////////////////////////////////////////////////////////////////////

/** 设置error异常处理函数。*/
function error_handler($errno, $errstr, $errfile, $errline )
{
	#不显示系统错误，否则抛出exception异常
	if( ! C('show_errors') ) return true;
	
	# 抛出系统异常
	throw new ephpException($errstr, $errno, array('errfile'=>$errfile, 'errline'=>$errline) );
}

/** 显示系统运行时间,数据库查询次数。 **/
function run_info()
{
	global $run_start_time,$run_dbquery_count;
	echo '<br />运行耗时:' . (microtime(1) - $run_start_time)*1000 . 'ms 数据库查询:'.$run_dbquery_count .'次';
}

/** dump()输出后，停止。 **/
function dumpdie($thing)
{dump($thing);exit;}

/** print输出后，停止。 **/
function printdie($thing)
{dump($thing,null,0);exit;}

/**
 * 1.浏览器友好的变量输出，var支持任何变量，echo表示是否需要输出，如果为否，则返回要显示的字符串。
 * 2.Strict表示是否输出详细信息，如果为否，使用print_r输出，如果为是，使用var_dump输出。
 * 3.Dump函数还支持xdebug扩展
 * @param string  $var
 * @param boolean $echo
 * @param string  $label
 * @param boolean $strict
 * @return void
 */
function dump($var, $label=null, $strict=true, $echo=true)
{
    $label = empty($label) ? '' : '<h2 style="color: red;font: bold;">'. rtrim($label) . ":</h2>\n";
    if(!$strict)
    {
		$output = print_r($var, true);
		$output = "<pre>".$label.htmlspecialchars($output,ENT_QUOTES)."</pre>";
    }
    else
    {
        ob_start();
        var_dump($var);
        $output = ob_get_clean();
        if(!extension_loaded('xdebug'))
        {
            $output = preg_replace('/\]\=\>\n(\s+)/m', "] => ", $output);
            $output = '<pre>'. $label . htmlspecialchars($output, ENT_QUOTES). '</pre>';
        }
        else $output = $label.$output;
    }

    if ($echo) echo $output;
    else return $output;
}


/**
 * 读取 or 设置配置文件的值
 * @param string $name 获取配置名称, 缺省返回所有配置，支持用'.'读取或设置二维数组
 * @param string $config_name 从哪个配置文件中从获取，默认main
 * @param mixed  $value 要导入的value
 */
function C($name='', $config_name='main', $value=null)
{
    static $_config = array();
    
    if( $value == null )
    {//获取值
    	
	    #加载其他配制文件
	    if($config_name != 'main' && !isset( $_config[$config_name] ))
	    {
	    	$filename = APP_PATH .'/conf/'. $config_name .'.config.php';
	    	if( file_exists($filename) ) $_config[$config_name] = include $filename;
	    	else show_error("配置文件：{$filename} 不存在！");
	    }
	    
    	// 返回需要value
	    if($name == '') return $_config[$config_name];
		else if( array_key_exists($name, $_config[$config_name]) )
	    	return $_config[$config_name][$name];
	    else if( strpos($name,'.') )
	    {
	        $array = explode('.',$name );
	        return isset($_config[$config_name][ $array[0] ][ $array[1] ]) ? $_config[$config_name][ $array[0] ][ $array[1] ] : false;
	    }
	    else return false;
    }
    else if( is_array($value) )
    {//初始化设置，批量导入
    	$_config[$config_name] = $value;
    	return true;
    }
	else
	{//单独改变一个配置的值
        if(! strpos($name,'.') ) $_config[$config_name][$name] = $value;
        else
        {//支持二维数组
            $array = explode('.',$name);
			$_config[$config_name][$array[0]][$array[1]] = $value;
        }
        return true;
    }
}

/** class auto load **/
function __autoload($classname)
{
	//echo $classname.'<hr />';
	#加载控制器
	if(substr($classname,-10) == 'Controller')
	{
		if(file_exists(APP_PATH.'/controllers/'.$classname.'.php')) include APP_PATH.'/controllers/'.$classname.'.php';
		else if(! C('debug') )  show_404();
		else show_error($classname .'没有定义，可能原因controllers/'. $classname .'.php不存在！');

	}#加载用户模型
	elseif(substr($classname,-5) == 'Model')
	{
		if(file_exists(APP_PATH.'/models/'.$classname.'.php')) include APP_PATH.'/models/'.$classname.'.php';
		else show_error('模型：'.APP_PATH.'/models/'.$classname.'.php 文件不存在!');
	}
	else if($classname == 'db') include FW_PATH.'/dbdrivers/'. C('dbdriver') .'.class.php';
	else
	{#加载第三方库，位置在ext/下
		if( file_exists(FW_PATH.'/libraries/'.$classname.'.class.php') ) include FW_PATH.'/libraries/'.$classname.'.class.php';
		else if( file_exists(APP_PATH.'/exts/'.$classname.'.class.php') ) include APP_PATH.'/exts/'.$classname.'.class.php';
		else show_error('扩展类：'.APP_PATH.'/exts/'.$classname.'.class.php 文件不存在!');
	}
}

/**
 * 快速创建model模型，相当于 new testModel()
 * @param string $model_name 模型名
 * @param string $db_config_name 要连接的数据库配制名，默认为空
 * @return object 模型对象
 */
function M($model_name, $db_config_name='')
{
	$model_name = $model_name . 'Model';
	$obj = new $model_name();

	if( $db_config_name != '') $obj->dbconfig($db_config_name);
	return $obj;
}

/**
 * 不需要创建model文件，快速实例化模型对象
 * @param string $model_name 模型名
 * @param string $db_config 要连接的数据库配制，默认default
 * @return object
 */
function QM($model_name, $db_config_name='default')
{
	$obj = new model;
	if( $db_config_name != 'default') $obj->dbconfig($db_config_name);
	$obj->table( C($db_config_name.'.tb_prefix','db' ).$model_name );
	return $obj;
}

/**
 * write日志
 * @param string $name 日志名称，如果带‘/’则生成文件夹
 * @param string $value 日志内容
 */
function wlog($name, $value)
{
	if(! is_writeable(APP_PATH . '/runtime/logs/' )) show_error('警告： _app/runtime/logs/ 目录不可写！');
	error_log($value."\n", 3, APP_PATH . '/runtime/logs/' . $name);
}


/**
 * URL装配车间,生成url，
 * 例子:U('index/help', array('id'=>25)) 或 U('index/help','id/15');
 * 那么可能生成：http://host/project/index.php/index/help/id/25.html
 *
 * @param string $base 控制器和方法
 * @param array  $param_arr url其他参数,接受参数array或string
 * @return string url 生成后的url字符串
 */
function U($base, $param_arr=array())
{
	#重载系统的U方法
	#if( function_exists('MY_U') ) return MY_U($base,$others_url);
	
	$urlx = '';
	if(C('url_type') != 'GET')
	{//生成PATH_INFO方式的url
		foreach ($param_arr as $k=>$v) if($v !== '') $urlx.='/'.$k.'/'.$v;
		
		return URL.'/'. $base . $urlx . C('html_url_suffix');
	}
	else
	{#GET方式的url
		$tarr = explode('/', $base);
		
		#第一个参数，出控制器和action外，可能包含其他的参数。
		foreach ($tarr as $k=>$v)
		{
			if($k == 0)		  $urlx .= '?controller='.$v;
			elseif($k == 1)   $urlx .= '&action='.$v;
			elseif($k%2 == 0) $urlx .= "&{$v}=";
			elseif($k%2 == 1) $urlx .= $v;
		}
			
		#第二个额外参数
		if(!empty($param_arr)) $urlx .= '&'.http_build_query($param_arr);
		
		return URL.$urlx;
	}
}


/** 显示404页面，并发送404 header **/
function show_404()
{
	#header('HTTP/1.1 404 Not Found');
	if( C('tpl_404') == '' ) include FW_PATH.'/tpl/404.tpl.html';
	else include APP_PATH.'/views/public/'.C('tpl_404');
	exit;
}

/**
 * 操作失败
 * @param string  $message 错误消息
 * @param string  $url 自动回跳的url
 * @param integer $wait 自动回跳等待时间，默认6s
 */
function show_error($message, $url='', $wait=6)
{
	#header('HTTP/1.1 500 Internal Server Error');
	//if( $url == '' && !empty($_SERVER['HTTP_REFERER']) ) $url = $_SERVER['HTTP_REFERER'];

	if( C('tpl_error') == '' ) include FW_PATH.'/tpl/500.tpl.html';
	else include APP_PATH.'/views/public/'.C('tpl_error');
	exit;
}

/**
 * 操作成功
 * @param string  $message 提示信息
 * @param string  $url     自动回跳的url
 * @param integer $wait    自动回跳等待时间，默认6s
 */
function show_success($message, $url='', $wait=6)
{
	if( $url == '' && isset($_SERVER['HTTP_REFERER']) ) $url = $_SERVER['HTTP_REFERER'];

	if( C('tpl_success') == '' ) include FW_PATH.'/tpl/success.tpl.html';
	else include APP_PATH.'/views/public/'.C('tpl_success');
	exit;
}

/**
 * 永久性质的跳转
 * @param string  $url 要跳转的url
 * @param integer $wait 跳转等待时间，默认0s
 * @param string  $message 提示信息
 */
function R($url,$wait=0,$message='')
{
	#header("HTTP/1.1 301 Moved Permanently");
    if(empty($message)) $message = "系统将在{$wait}秒之后自动跳转到{$url}！";
    if (!headers_sent() && (0===$wait) )
    {
        // redirect
        header("Content-Type:text/html; charset=UTF-8");
		header("Location: {$url}");
	}
    else
    {
    	//html refresh
    	#header("refresh:{$wait};url={$url}"); #直接发送header头。
		include FW_PATH.'/tpl/301.tpl.html';
		exit;
    }
}

/**
 * 获取$_GET中的值，如果不存在返回空字符串,不传参数获取所有的get内容
 * @param string $key
 * @param string $default
 */
function getv($key=0, $default='')
{
	if(! $key) return $_GET;
	return isset($_GET[$key]) ?  $_GET[$key] : $default;
}

/**
 * 获取$_POST中的值，如果不存在返回空字符串,不传参数获取所有的post内容
 * @param string $key
 * @param string $default
 */
function postv($key=0, $default='')
{
	if(! $key) return $_POST;
	return isset($_POST[$key]) ? $_POST[$key] : $default;
}

/**
 * 获取$_REQUEST中的值，如果不存在返回空字符串,不传参数获取所有的request内容
 * @param string $key
 * @param string $default
 */
function requestv($key=0, $default='')
{
	if(! $key) return $_REQUEST;
	return isset($_REQUEST[$key]) ? $_REQUEST[$key] : $default;
}

///////////////////////////////////////////////////////////////////////////////////
// core class
///////////////////////////////////////////////////////////////////////////////////

#调度类
class app
{
	public function __construct()
	{
		$path_info = $this->_path_info();
		if(! empty($path_info)) $splits = explode('/', trim($path_info, '/'));
		else $splits='';
		
		$_GET['controller'] = isset($_GET['controller']) ? $_GET['controller'] : 'index';
		$_GET['action'] 	= isset($_GET['action']) ? $_GET['action']  : 'index';
			
		#在保证安全的前提下，进行兼容PATH_INFO和GET方式，如果是url混合方式，则以path_info为主
		if( isset($splits[0]) )
		{
			$_GET['controller'] = $splits[0];
			$_GET['action'] = isset($splits[1]) ? $splits[1] : 'index';
		}

		$ucount = count($splits);
		for($i=2; $i<$ucount; $i+=2)
		{
			if( isset($splits[$i]) && isset($splits[$i+1])) $_GET[$splits[$i]]=$splits[$i+1];
		}
		
		if( is_array($_GET)) $_REQUEST = array_merge($_GET, $_REQUEST);
	}

	/** 运行用户控制器。 **/
	public function run()
	{
		try
		{
			$controller_name = $_GET['controller'].'Controller';
			$action_name = $_GET['action'].'Action';
			
			if(method_exists($controller_name, $action_name))
			{
				$c_init = new $controller_name;
				call_user_func( array($c_init, $action_name) );
			}
			else if(! C('debug') ) show_404();
			else show_error("在控制器 <b>{$controller_name}</b> 中 <b>{$action_name}()</b> 未定义！ ");
		}
		catch (ephpException $e)
		{
			echo $e;
		}
	}
	
	/** 获取PAHT_INFO，没有则返回空字符串。 **/
	private function _path_info()
	{
		$path_info='';
		if(! empty($_SERVER['PATH_INFO'])) #冗余避免触发E_NOTICE错误；
		{
			$path_info = $_SERVER['PATH_INFO'];

			//启用url路由功能
			if( C('url_router') ) #是否打开路由开关
			{
				$first_param = substr($path_info,1,strpos($path_info,'/',1) - 1); #获取url上的第一个参数，用于对象router中的路由规则；
				$config = include APP_PATH.'/conf/router.config.php';

				//var_dump($config[$first_param]);
				if( isset($config[$first_param])) #冗余避免触发E_NOTICE错误；
				{
					foreach ($config[$first_param] as $v)
					{
						$count = 0; //记录成功替换的个数
						$path_info = preg_replace($v[0],$v[1],$path_info,-1,$count);
						//var_dump($path_info);

						//只要匹配上一个，则停止匹配，故在router.config.php从上到下有优先规则。
						if($count > 0) break;

						//记录路由还原后的PATH_INFO到GET数组
						//$_GET['PATH_INFO'] = $path_info;
					}
				}
			}
			
			//去掉path_info 后缀,这里不能用trim，如果是trim则会把t也会去掉
			$html_url_subffix = C('html_url_suffix');
			if( $html_url_subffix ) $path_info = str_replace($html_url_subffix, '', $path_info);
		}
		return $path_info;
	}
}

#非必须父控制器
class controller
{
	/**
     * Dynamic get vars
     * @param string $key
     */
	public function __get($key)
	{
		//echo $key.'<hr />';
		switch ($key)
		{
			case 'view': return $this->view = new view; break;
			case 'request':
				include FW_PATH.'/libraries/Request.class.php';
				return $this->request = new Request;
				break;
			case 'validate':
				include FW_PATH.'/libraries/Validate.class.php';
				return $this->validate = new Validate;
			case substr($key,0,5) == 'model':
				#引入model类
				include FW_PATH.'/libraries/model.class.php';
				include FW_PATH.'/dbdrivers/'. C('dbdriver') .'.class.php';
				
				$default_model = substr(get_class($this), 0, -10);
				if($key == 'model')
					$model_name =  $default_model. 'Model';
				else
					$model_name = substr($key,6) .'Model';
					
				$model_path = APP_PATH . "/models/{$model_name}.php";
				if( file_exists($model_path) )
				{
					include $model_path;
					return $this->$model_name = new $model_name;
				}
				else
				{//不指定模型名, 在控制器中直接$this->model->table()->find()
					$this->model = new model;
					//$this->model->table( C('default.tb_prefix','db').$default_model );
					return $this->model;
				}
				break;
			case 'cache':
				include FW_PATH.'/libraries/Cache.class.php';
				return $this->cache = Cache::init();
			default: show_error("Undefined property {$key}");
			
		}
	}
}

#视图类
class view
{
	private $tVar=array(); //模板变量assign
	
	/**
	 * 视图渲染
	 * @param string $file 视图路径，_app/views/下的路径部分
	 */
	public function render($file='')
	{
		// 只写controller和action可以省略.tpl.php,比如，$view->render('index/index');
		// 默认渲染当前控制器、当前action的.tpl.php文件
		if(empty($file)) $file = $_GET['controller'] . '/' . $_GET['action'].'.tpl.php';
		else if( ! strpos($file, '.php')) $file = $file.'.tpl.php';
			
		//模板全路径
		$filename = APP_PATH . '/views/'.$file;
		
		if(! file_exists($filename)) show_error("视图文件：{$filename} 不存在，请检查以确认。");
		
		/** 释放assign的模板变量 **/
		if(! empty($this->tVar)) extract($this->tVar);
			
		/* 使可以使用$view->xxx,代替$this->xxx保持和controller里的一致 */
		$view = $this;
			
		/** 渲染模板 **/
		if(! C('strip_html_whitespace'))
			include $filename;
		else
		{
			ob_start();
			include $filename;
			echo preg_replace(array('/[ ]{2}/', '/[\r\n]/'),array(' ',''), ob_get_clean());
		}
		
	}
	
	/**
	 * 渲染模板视图
	 * render()的别名
	 * @param string $file 视图路径
	 */
	public function display($file='') { $this->render($file); }
	
	/**
	 * 模板变量赋值
	 * @param string $name 变量名称
	 * @param mixed  $value
	 */
    public function assign($name,$value='') { $this->tVar[$name] = $value; }
}

///////////////////////////////////////////////////////////////////////////////////
// 运行环境初始化
///////////////////////////////////////////////////////////////////////////////////
#设置error异常处理函数
set_error_handler("error_handler");

#默认系统配置
$config = array
(
	'debug'				=> false,
	'access_log' 		=> false,
	'exception_log' 	=> false,
	'sql_log' 			=> false,
	'show_errors'		=> false,

	'html_url_suffix'	=> '',
	'url_router'		=> false,
	'url_type'			=> 'PATH_INFO',

	'dbdriver'			=> 'mysqli',

	'session_start'		=> false,
	'assets_dir'		=> '',

	'strip_html_whitespace'	=> false
);

#导入系统配置文件
if( file_exists(APP_PATH.'/conf/main.config.php') )
{
	$config_main = include APP_PATH.'/conf/main.config.php';
	$config = $config_main + $config;
	
	//如果debug为true，则下面四项强制为true
	if( $config['debug'] )
	{
		$config['access_log'] 		= true;
		$config['exception_log']	= true;
		$config['sql_log'] 			= true;
		$config['show_errors'] 		= true;
	}
}
C('', 'main',$config);

#设置资源目录的位置
if($config['assets_dir'] == '')
{//如果没有配置assets_dir，则采用当前目下的assets/作为资源目录
	$source_dir = ( ($sdir = dirname($_SERVER['SCRIPT_NAME'])) == '/' ) ? '' : $sdir;
	define("SOURCE", $source_dir.'/assets' );
}
else define('SOURCE', $config['assets_dir']);

#时区 & session 设置
//date_default_timezone_set($config['date_timezone']);

#定义url常量
if($config['url_type'] != 'SEO') define("URL", $_SERVER['SCRIPT_NAME']); #带入口文件index.php,例如:/book/index.php
else #不带index.php的url，例如：/book/
{
	if(! isset($source_dir) ) $source_dir = ( ($sdir = dirname($_SERVER['SCRIPT_NAME'])) == '/' ) ? '' : $sdir;
	define("URL", $source_dir);
}

#显示系统错误
if($config['show_errors'])
{
	ini_set('display_errors', 'On');
	error_reporting(E_ALL | E_STRICT);
}
else
{
	ini_set('display_errors', 'Off');
	error_reporting(0);
}

#是否启用session, 如果session.auto_start=0的时候，才需要session_start()
if ( $config['session_start'] && ! ini_get('session.auto_start'))  session_start();

#记录系统访问日志
if($config['access_log'])
{
	$str ="\nREQUEST_URI:http://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']."\n";
	$str.='REMOTE_ADDR:'.$_SERVER['REMOTE_ADDR']."\n";
	$str.=isset($_SERVER['HTTP_USER_AGENT']) ? 'HTTP_USER_AGENT:'.$_SERVER['HTTP_USER_AGENT']."\n" : '';
	$str.='------------------------------';
	wlog('AccessLog-'.date('Y-m-d').'.log', '['. date('H:i:s') .']'.$str);
}
//echo '<br />cost:',(1000 * (microtime(1) - $run_start_time) ),' ms';

/* End of file ePHP.php */
/* Location: ./_framework/ePHP.php */